Using Mutexes and Lock Guards
A mutex (mutual exclusion) is a lockable object that is designed to signal when critical sections of code need exclusive access. Mutexes are used to prevent multiple threads from accessing shared data. Since threads execute on different CPUs at the same time, the mutex effectively ensures that only one core holds the lock at any time.
Attention
When using mutexes with a custom type, an important design consideration is whether you want the lock to exist within the class (in which can it can be a member variable initialized in the constructor of the class), or initalized outside the class and passed to the constructor. The former case implies that no other classes also need access to the shared memory. If this is not the case, it is incorrect to initialize the mutex within the class itself. See Examples.
Which Lock to Use?
Read the accepted answer here for appropriate uses of std::scoped_lock
, std:lock_guard
, and std::unqiue_lock
: std::lock_guard or std::scoped_lock?
Use the simplest tool for the job
lock_guard
if you need to lock exactly 1 mutex for an entire scope.scoped_lock
if you need to lock more than 1 mutexes for an entire scope.unique_lock
if you need to unlock within the scope of the block (which includes use with acondition_variable
).However, contention lies around this point, and my personal preference is to use
scoped_lock
for everything unlessunique_lock
is required.
Examples
Example: Scoped lock with a function
#include <mutex>
std::mutex a;
std::mutex b;
void worker() {
std::scoped_lock guard(a, b);
// Do some thing that is guarded by the lock
// RAII lock is freed when scope is left
}
#include <mutex>
std::mutex a;
std::mutex b;
void worker() {
std::scoped_lock guard(a, b);
// Do some thing that is guarded by the lock
// RAII lock is freed when scope is left
}
Example: Unqiue/scoped lock passed to a class
/// worker.h
class Worker {
public:
// bind reference to mutex in initializer list
Worker(std::mutex& mtx) : _mtx(mtx){}
void start();
private:
std::mutex& _mtx;
};
/// worker.cpp
void Worker::start() {
// lock the shared resource
std::scoped_lock<std::mutex> guard(_mtx);
// Do some thing that is guarded by the lock
// RAII lock is freed when scope is left
}
/// main.cpp
int main() {
std::mutex mtx;
// create a vector of workers that all share the mutex
std::vector<Worker> workers(4, Worker(std::ref(mtx)));
// ...
}
/// worker.h
class Worker {
public:
// bind reference to mutex in initializer list
Worker(std::mutex& mtx) : _mtx(mtx){}
void start();
private:
std::mutex& _mtx;
};
/// worker.cpp
void Worker::start() {
// lock the shared resource
std::scoped_lock<std::mutex> guard(_mtx);
// Do some thing that is guarded by the lock
// RAII lock is freed when scope is left
}
/// main.cpp
int main() {
std::mutex mtx;
// create a vector of workers that all share the mutex
std::vector<Worker> workers(4, Worker(std::ref(mtx)));
// ...
}
Example: Unqiue/scoped lock initialized in a class
/// worker.h
class Worker {
public:
// instantiate internal mutex in initializer list
Worker() : _mtx() {}
void start();
private:
std::mutex _mtx;
};
/// worker.cpp
void Worker::start() {
// lock the shared resource
std::unique_lock<std::mutex> guard(_mtx);
// Do something with it here, RAII lock is
// freed when scope is left
}
/// main.cpp
int main() {
std::vector<Worker> workers(4, Worker());
// ...
}
/// worker.h
class Worker {
public:
// instantiate internal mutex in initializer list
Worker() : _mtx() {}
void start();
private:
std::mutex _mtx;
};
/// worker.cpp
void Worker::start() {
// lock the shared resource
std::unique_lock<std::mutex> guard(_mtx);
// Do something with it here, RAII lock is
// freed when scope is left
}
/// main.cpp
int main() {
std::vector<Worker> workers(4, Worker());
// ...
}
Appendix
RESOURCES
- c++ - std::lock_guard or std::scoped_lock? - Stack Overflow
- c++ - std::scoped_lock or std::unique_lock or std::lock_guard? - Stack Overflow
- std::scoped_lock - cppreference.com
- c++ - How does scope-locking work? - Stack Overflow
- https://stackoverflow.com/questions/52858761/passing-mutex-reference-from-main-to-a-class
- Harvard article on multi-threaded synchronization in C++